In [7]:
import types
import pickle
import copy_reg

In [9]:
# why functions with closures can't be serialised

# this generates a Cell object, which represents a variable in a closure
create_cell = lambda x: (lambda: x).func_closure[0]

# create our function with a closure
def mk ():
    x = 5
    y = mk
    def f ():
        return x, y
    return f

f = mk()

# this...
g = pickle.loads(pickle.dumps(CannedFunction(f))).getObject()

# ...essentially does (would do) this
co = f.func_code
co = types.CodeType(co.co_argcount, co.co_nlocals, co.co_stacksize,
                    co.co_flags, co.co_code, co.co_consts, co.co_names,
                    co.co_varnames, co.co_filename, co.co_name,
                    co.co_firstlineno, co.co_lnotab)
closure = f.func_closure
if closure is not None:
    closure = tuple(create_cell(obj) for obj in f.func_closure)
# recreating the code object (co) makes the function 'want' a 0-length
# closure
g = types.FunctionType(co, f.func_globals, f.func_name, f.func_defaults,
                        closure)


---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-9-77cb2e93db04> in <module>()
     15 
     16 # this...
---> 17 g = pickle.loads(pickle.dumps(CannedFunction(f))).getObject()
     18 
     19 # ...essentially does (would do) this

<ipython-input-5-3280fc0df14e> in getObject(self, g)
     43         closure = tuple(create_cell(obj) for obj in self.closure)
     44         newFunc = types.FunctionType(self.code, g, self.__name__,
---> 45                                      self.defaults, closure)
     46         return newFunc

ValueError: f requires closure of length 0, not 2

In [10]:
# some modified function serialisation stuff from ipython

def code_ctor(*args):
    return types.CodeType(*args)

def reduce_code(co):
    #if co.co_freevars or co.co_cellvars:
        #raise ValueError("Sorry, cannot pickle code objects with closures")
    args = [co.co_argcount, co.co_nlocals, co.co_stacksize,
            co.co_flags, co.co_code, co.co_consts, co.co_names,
            co.co_varnames, co.co_filename, co.co_name, co.co_firstlineno,
            co.co_lnotab]
    if sys.version_info[0] >= 3:
        args.insert(1, co.co_kwonlyargcount)
    return code_ctor, tuple(args)

copy_reg.pickle(types.CodeType, reduce_code)

class CannedFunction (object):
    def __init__(self, f):
        self._checkType(f)
        self.code = f.func_code
        self.closure = [cell.cell_contents for cell in f.func_closure]
        self.defaults = f.func_defaults
        self.module = f.__module__ or '__main__'
        self.__name__ = f.__name__

    def _checkType(self, obj):
        assert isinstance(obj, types.FunctionType), "Not a function type"

    def getObject(self, g=None):
        # try to load function back into its module:
        if not self.module.startswith('__'):
            try:
                __import__(self.module)
            except ImportError:
                pass
            else:
                g = sys.modules[self.module].__dict__

        if g is None:
            g = globals()
        closure = tuple(create_cell(obj) for obj in self.closure)
        newFunc = types.FunctionType(self.code, g, self.__name__,
                                     self.defaults, closure)
        return newFunc